package bll

import (
	"context"
	"fmt"
	"github.com/panjf2000/ants/v2"
	log "ota/collector/logger"
	"ota/errors"
	"ota/event"
	"ota/model"
	"ota/model/entity"
	"ota/model/mapping"
	"ota/store"
	"ota/store/postgres"
	"ota/utils"
	"time"
)

type userUpgrade struct {
	antPools     *ants.PoolWithFunc
	iUserUpgrade store.IUserUpgrade
	iUpgradePlan store.IUpgradePlan
}

var (
	UserUpgrade = &userUpgrade{
		iUserUpgrade: postgres.UserUpgrade,
		iUpgradePlan: postgres.UpgradePlan,
	}
)

func init() {
	Register(UserUpgrade)
}

func (a *userUpgrade) init() func() {
	return func() {}
}

func (a *userUpgrade) onEvent(*event.Data) {}

func (a *userUpgrade) check(ctx context.Context, id int64, op model.OtaUserUpgradeOpType, f func(upgrade *entity.UserUpgrade)) error {

	respUserUpgrade, err := a.iUserUpgrade.Find(ctx, &model.UserUpgradeInfoRequest{Id: id})
	if err != nil {
		log.Errorf("get user upgrade err: %v", err)
		return errors.InternalServerError.Error()
	}
	if respUserUpgrade.Id == 0 {
		return errors.UpgradeStrategyNotExist.Error()
	}

	if f != nil {
		f(respUserUpgrade)
	}

	return nil
}

// Create
func (a *userUpgrade) Create(ctx context.Context, in *model.UserUpgradeCreateRequest) error {
	var (
		err error
	)
	c := buildUserUpgrade(in)
	_, err = a.iUserUpgrade.Create(ctx, c)
	return err
}

// CreateInBatch
func (a *userUpgrade) CreateInBatch(ctx context.Context, ins []*model.UserUpgradeCreateRequest, c int) error {

	var (
		err error
		es  []*entity.UserUpgrade
	)

	for _, in := range ins {
		es = append(es, buildUserUpgrade(in))
	}

	err = a.iUserUpgrade.CreateInBatch(ctx, es, c)
	for i, _ := range es {
		ins[i].Id = es[i].Id
	}
	return err
}

func (a *userUpgrade) Retry(ctx context.Context, ins *model.UserUpgradeRetryRequest) error {

	var (
		index                = 1
		size                 = 100
		planId2Ids           = map[int64][]int64{}
		planId2RetryRequests = map[int64][]*model.OtaUpgradeRequest{}
		userUpgradeStatus    = int(model.UpgradeResultFailure)
	)
	for {
		_, l, err := a.iUserUpgrade.List(context.Background(), &model.UserUpgradeListRequest{
			Ids:          ins.Ids,
			Status:       &userUpgradeStatus,
			EbikeSnCodes: ins.SnCode,
			EbikeTypeIds: ins.EbikeIds,
			PlanIds:      ins.PlanIds,
			Index:        index,
			Size:         size,
		}, model.QueryOptions{OnlyList: true})
		if err != nil {
			return errors.Wrap(err, "list user upgrade fail")
		}

		for _, u := range l {
			if u.Status != int(model.UpgradeResultFailure) {
				//return fmt.Errorf("已被执行终止的升级计划(%v,%v)不可以再执行重试", u.PlanId, u.PlanName)
				return fmt.Errorf("只可以重试失败的任务")
			}

			planId2Ids[u.PlanId] = append(planId2Ids[u.PlanId], u.Id)
			planId2RetryRequests[u.PlanId] = append(planId2RetryRequests[u.PlanId], &model.OtaUpgradeRequest{
				ID:         u.Id,
				SNCode:     u.EbikeSnCode,
				Status:     utils.GetValuePointer(model.OtaAgree),
				PlanId:     u.PlanId,
				StrategyId: u.StrategyId,
			})

		}

		if len(l) < size {
			break
		}
		index = index + 1
	}

	if len(planId2RetryRequests) == 0 {
		return fmt.Errorf("没有可以重试的任务")
	}

	for planId, retryRequests := range planId2RetryRequests {

		ids, ok := planId2Ids[planId]
		if !ok {
			continue
		}

		err := a.iUpgradePlan.ExecTransaction(ctx, func(ctx context.Context) error {
			if err := a.iUpgradePlan.Update(context.Background(), planId, map[string]interface{}{
				"updated_at": time.Now().Unix(),
				"status":     model.UpgradePlanStatusUpgrading,
			}); err != nil {
				return err
			}

			if err := a.iUserUpgrade.Updates(context.Background(), ids, map[string]interface{}{
				"updated_at": time.Now().Unix(),
				"status":     model.UpgradeResultPending,
			}); err != nil {
				return err
			}

			if err := upPendingTimer.submitUpgradeTask(retryRequests); err != nil {
				log.Errorf("submit retry task err:%+v", err)
				return err
			}

			return nil

		})

		if err != nil {
			log.Errorf("params:(ids:%v plan_id:%+v sn code:%+v) ,exec retry task(plan_id:%v) err:%+v", ins.Ids, ins.PlanIds, ins.SnCode, planId, err)
			return errors.InternalServerError.Error()
		}
	}

	return nil
}

func (a *userUpgrade) Update(ctx context.Context, in *model.UserUpgradeUpdateRequest) error {

	var (
		dict         = make(map[string]interface{})
		updateStatus bool
	)

	if in.UserId != nil {
		dict["user_id"] = in.UserId
	}

	if in.UserName != nil {
		dict["user_name"] = in.UserName
	}

	if in.PlanId != nil {
		dict["plan_id"] = in.PlanId
	}

	if in.PlanName != nil {
		dict["plan_name"] = in.PlanName
	}

	if in.EbikeId != nil {
		dict["ebike_id"] = in.EbikeId
	}

	if in.EbikeName != nil {
		dict["ebike_name"] = in.EbikeName
	}

	if in.EbikeSnCode != nil {
		dict["ebike_sn_code"] = in.EbikeSnCode
	}

	if in.EbikeTypeId != nil {
		dict["ebike_type_id"] = in.EbikeTypeId
	}

	if in.EbikeTypeName != nil {
		dict["ebike_type_name"] = in.EbikeTypeName
	}

	if in.Detail != nil {
		dict["detail"] = in.Detail
	}

	if in.Progress != nil {
		dict["progress"] = in.Progress
	}

	if in.Status != nil {
		updateStatus = true
		dict["status"] = in.Status
	}

	if in.StatusDetail != nil {
		dict["status_detail"] = in.StatusDetail
	}

	if in.FailureReason != nil {
		dict["failure_reason"] = in.FailureReason
	}

	if in.FailureLatestTime != nil {
		dict["failure_latest_time"] = in.FailureLatestTime
	}

	if in.RetryCount != nil {
		dict["retry_count"] = in.RetryCount
	}

	if in.CreatedAt != nil {
		dict["created_at"] = in.CreatedAt
	}

	if in.UpdatedAt != nil {
		dict["updated_at"] = in.UpdatedAt
	}

	// do other update here
	updateAt := time.Now().Unix()
	in.UpdatedAt = &updateAt

	if in.Id != nil && *in.Id != 0 && len(in.Ids) == 0 {
		return a.iUserUpgrade.ExecTransaction(ctx, func(ctx context.Context) error {
			userUpgradeCache.Remove(*in.Id)

			if err := a.iUserUpgrade.Update(ctx, *in.Id, dict); err != nil {
				return err
			}

			if updateStatus {
				userUpgradeStatusCache.Add(*in.Id, model.OtaUpgradeResultType(*in.Status))
			}
			return nil
		})
	}

	if len(in.Ids) > 0 {
		return a.iUserUpgrade.ExecTransaction(ctx, func(ctx context.Context) error {
			for _, ids := range utils.SplitSlice(in.Ids, 200) {
				for _, id := range ids {
					userUpgradeCache.Remove(id)
				}

				if err := a.iUserUpgrade.Updates(ctx, ids, dict); err != nil {
					return err
				}

				if updateStatus {
					for _, id := range ids {
						userUpgradeStatusCache.Add(id, model.OtaUpgradeResultType(*in.Status))
					}
				}
			}
			return nil
		})
	}

	return nil
}

// Terminal
func (a *userUpgrade) Terminal(ctx context.Context, in *model.UserUpgradeTerminalRequest) error {

	var (
		dict = map[string]interface{}{
			"status":     model.UpgradeResultAbolishment,
			"updated_at": time.Now().Unix(),
		}
		ids []int64
	)

	/*
	   取消升级只取消处于待升级状态的任务
	*/
	_, userUpgrades, err := a.iUserUpgrade.List(ctx, &model.UserUpgradeListRequest{
		Index:  1,
		Size:   -1,
		PlanId: utils.GetValuePointer(in.PlanId),
		Status: utils.GetValuePointer(int(model.UpgradeResultPending)),
	}, model.GetQueryOption(model.QueryOptions{OnlyList: true}))
	if err != nil {
		return err
	} else {
		for _, v := range userUpgrades {
			ids = append(ids, v.Id)
		}
	}
	if len(ids) == 0 {
		return fmt.Errorf("不存在可以终止的任务,只有待执行的任务可以被终止")
	}

	return a.iUserUpgrade.Updates(ctx, ids, dict)
}

// Delete
func (a *userUpgrade) Delete(ctx context.Context, in *model.UserUpgradeDeleteRequest) error {

	userUpgradeCache.Remove(in.Id)
	return a.iUserUpgrade.Delete(ctx, in.Id)
}

// Statistics
func (a *userUpgrade) Statistics(ctx context.Context, in *model.UserUpgradeStatisticsRequest) (*model.UserUpgradeStatisticsItem, error) {

	resp, err := a.iUserUpgrade.Statistics(ctx, in)
	if err != nil {
		return nil, err
	}

	var out = &model.UserUpgradeStatisticsItem{}
	if len(resp.List) == 0 {
		return out, nil
	}

	out = &resp.List[0]
	out.AllTotal = out.SuccessTotal + out.FailedTotal + out.PrepareTotal + out.UpgradingTotal + out.CancelTotal
	return out, nil
}

// List
func (a *userUpgrade) List(ctx context.Context, in *model.UserUpgradeListRequest, opts ...model.QueryOptions) (*model.UserUpgradeListResponse, error) {
	var (
		err         error
		total       int
		list        []*entity.UserUpgrade
		out         = &model.UserUpgradeListResponse{}
		defaultOpts = model.QueryOptions{OrderFields: model.NewOrderFields(model.NewOrderField("created_at", model.OrderByDESC))}
	)

	if len(opts) > 0 {
		defaultOpts = model.GetQueryOption(opts...)
	}

	if total, list, err = a.iUserUpgrade.List(ctx, in, defaultOpts); err != nil {
		return nil, err
	}

	out.Total = total
	out.List = mapping.UserUpgradesEntityToDto(list)

	return out, nil
}

// Find
func (a *userUpgrade) Find(ctx context.Context, in *model.UserUpgradeInfoRequest) (*model.UserUpgradeInfo, error) {
	var (
		err  error
		data *entity.UserUpgrade
		out  = &model.UserUpgradeInfo{}
	)

	if data, err = a.iUserUpgrade.Find(ctx, in); err != nil {
		return nil, err
	}

	out = mapping.UserUpgradeEntityToDto(data)
	return out, nil
}

// buildUserUpgrade build entity
func buildUserUpgrade(in *model.UserUpgradeCreateRequest) *entity.UserUpgrade {

	ety := &entity.UserUpgrade{}

	ety.Id = in.Id

	ety.UserId = in.UserId

	ety.UserName = in.UserName

	ety.UserPhone = in.UserPhone

	ety.PlanId = in.PlanId

	ety.PlanName = in.PlanName

	ety.StrategyId = in.StrategyId

	ety.UpgradeType = in.UpgradeType

	ety.EbikeId = in.EbikeId

	ety.EbikeName = in.EbikeName

	ety.EbikeSnCode = in.EbikeSnCode

	ety.EbikeTypeId = in.EbikeTypeId

	ety.EbikeTypeName = in.EbikeTypeName

	ety.EbikeTypeName = in.EbikeTypeName

	ety.Status = in.Status

	ety.StatusDetail = in.StatusDetail

	ety.UpgradingStatus = in.UpgradingStatus

	ety.FailureReason = in.FailureReason

	ety.Detail = in.Detail

	ety.RetryCount = in.RetryCount

	ety.CreatedAt = time.Now().Unix()

	ety.UpdatedAt = in.UpdatedAt

	return ety
}
